Несмотря на огромные вложения в рекламу, последние несколько месяцев компания терпит убытки. Наша задача — разобраться в причинах и помочь компании выйти в плюс. Цели исследования:
Задачи исследования выяснить:
Описание данных: Есть данные о пользователях, привлечённых с 1 мая по 27 октября 2019 года:
User Id — уникальный идентификатор пользователя,
Region — страна пользователя,
Device — тип устройства пользователя,
Channel — идентификатор источника перехода,
Session Start — дата и время начала сессии,
Session End — дата и время окончания сессии.
Структура orders_info_short.csv:
User Id — уникальный идентификатор пользователя,
Event Dt — дата и время покупки,
Revenue — сумма заказа.
Структура costs_info_short.csv:
dt — дата проведения рекламной кампании,
Channel — идентификатор рекламного источника,
costs — расходы на эту кампанию.
Ход исследования:
Загрузите данные о визитах, заказах и рекламных расходах из CSV-файлов в переменные.
Пути к файлам
/datasets/visits_info_short.csv. Скачать датасет;/datasets/orders_info_short.csv. Скачать датасет;/datasets/costs_info_short.csv. Скачать датасет.Изучите данные и выполните предобработку. Есть ли в данных пропуски и дубликаты? Убедитесь, что типы данных во всех колонках соответствуют сохранённым в них значениям. Обратите внимание на столбцы с датой и временем.
1.1 Импорт необходимых библиотек
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
import plotly.express as px
1.2 Загрузка и чтение данных из csv-файла в датафрейм c помощью библиотеки pandas
def data_full_info(dataset):
print (dataset.info())
return (dataset.head(10))
visits, orders, costs = (
pd.read_csv('/datasets/visits_info_short.csv'),
pd.read_csv('/datasets/orders_info_short.csv'),
pd.read_csv('/datasets/costs_info_short.csv'),
)
data_full_info(visits)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 309901 entries, 0 to 309900 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 User Id 309901 non-null int64 1 Region 309901 non-null object 2 Device 309901 non-null object 3 Channel 309901 non-null object 4 Session Start 309901 non-null object 5 Session End 309901 non-null object dtypes: int64(1), object(5) memory usage: 14.2+ MB None
| User Id | Region | Device | Channel | Session Start | Session End | |
|---|---|---|---|---|---|---|
| 0 | 981449118918 | United States | iPhone | organic | 2019-05-01 02:36:01 | 2019-05-01 02:45:01 |
| 1 | 278965908054 | United States | iPhone | organic | 2019-05-01 04:46:31 | 2019-05-01 04:47:35 |
| 2 | 590706206550 | United States | Mac | organic | 2019-05-01 14:09:25 | 2019-05-01 15:32:08 |
| 3 | 326433527971 | United States | Android | TipTop | 2019-05-01 00:29:59 | 2019-05-01 00:54:25 |
| 4 | 349773784594 | United States | Mac | organic | 2019-05-01 03:33:35 | 2019-05-01 03:57:40 |
| 5 | 90242400005 | United States | iPhone | RocketSuperAds | 2019-05-01 07:14:52 | 2019-05-01 08:19:56 |
| 6 | 43958116050 | United States | Android | organic | 2019-05-01 09:03:21 | 2019-05-01 10:08:39 |
| 7 | 185365244969 | United States | iPhone | organic | 2019-05-01 09:37:03 | 2019-05-01 10:00:43 |
| 8 | 446013509831 | United States | iPhone | RocketSuperAds | 2019-05-01 10:26:00 | 2019-05-01 10:59:17 |
| 9 | 100970711362 | United States | Mac | TipTop | 2019-05-01 04:39:53 | 2019-05-01 04:57:05 |
data_full_info(orders)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 40212 entries, 0 to 40211 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 User Id 40212 non-null int64 1 Event Dt 40212 non-null object 2 Revenue 40212 non-null float64 dtypes: float64(1), int64(1), object(1) memory usage: 942.6+ KB None
| User Id | Event Dt | Revenue | |
|---|---|---|---|
| 0 | 188246423999 | 2019-05-01 23:09:52 | 4.99 |
| 1 | 174361394180 | 2019-05-01 12:24:04 | 4.99 |
| 2 | 529610067795 | 2019-05-01 11:34:04 | 4.99 |
| 3 | 319939546352 | 2019-05-01 15:34:40 | 4.99 |
| 4 | 366000285810 | 2019-05-01 13:59:51 | 4.99 |
| 5 | 129100331057 | 2019-05-01 01:56:28 | 4.99 |
| 6 | 626807144131 | 2019-05-01 02:39:21 | 4.99 |
| 7 | 914062168985 | 2019-05-01 02:02:18 | 4.99 |
| 8 | 769669137453 | 2019-05-01 03:39:47 | 4.99 |
| 9 | 434198630691 | 2019-05-01 18:16:50 | 5.99 |
data_full_info(costs)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1800 entries, 0 to 1799 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 dt 1800 non-null object 1 Channel 1800 non-null object 2 costs 1800 non-null float64 dtypes: float64(1), object(2) memory usage: 42.3+ KB None
| dt | Channel | costs | |
|---|---|---|---|
| 0 | 2019-05-01 | FaceBoom | 113.3 |
| 1 | 2019-05-02 | FaceBoom | 78.1 |
| 2 | 2019-05-03 | FaceBoom | 85.8 |
| 3 | 2019-05-04 | FaceBoom | 136.4 |
| 4 | 2019-05-05 | FaceBoom | 122.1 |
| 5 | 2019-05-06 | FaceBoom | 118.8 |
| 6 | 2019-05-07 | FaceBoom | 101.2 |
| 7 | 2019-05-08 | FaceBoom | 100.1 |
| 8 | 2019-05-09 | FaceBoom | 93.5 |
| 9 | 2019-05-10 | FaceBoom | 104.5 |
3 дата-фрейма хранят в себе данные о визитах в приложение, совершенных заказов и расходах приложения. Во всех датасетах не наблюдается пропусков. Необходимо внести изменения в названия столбцов таблиц для комфортной работы и изменить типы данных, что важно для последующего анализа.
1.3 Предобработка данных
Переименование столбцов в соотвествии со стилем snake_case.
visits = visits.rename(columns=str.lower)
orders = orders.rename(columns=str.lower)
costs = costs.rename(columns=str.lower)
visits = visits.rename(columns={'user id': 'user_id', 'session start': 'session_start', 'session end': 'session_end'})
orders = orders.rename(columns={'user id': 'user_id', 'event dt': 'event_dt'})
Проверка пропусков
visits.isna().sum()
user_id 0 region 0 device 0 channel 0 session_start 0 session_end 0 dtype: int64
orders.isna().sum()
user_id 0 event_dt 0 revenue 0 dtype: int64
costs.isna().sum()
dt 0 channel 0 costs 0 dtype: int64
Пропусков не найдено.
Проверка дата-фреймов на наличие дубликатов
visits.duplicated().sum()
0
orders.duplicated().sum()
0
costs.duplicated().sum()
0
display('Регионы', visits['region'].unique())
display('Каналы', visits['channel'].unique())
display('Устройства', visits['device'].unique())
'Регионы'
array(['United States', 'UK', 'France', 'Germany'], dtype=object)
'Каналы'
array(['organic', 'TipTop', 'RocketSuperAds', 'YRabbit', 'FaceBoom',
'MediaTornado', 'AdNonSense', 'LeapBob', 'WahooNetBanner',
'OppleCreativeMedia', 'lambdaMediaAds'], dtype=object)
'Устройства'
array(['iPhone', 'Mac', 'Android', 'PC'], dtype=object)
display('Каналы', costs['channel'].unique())
'Каналы'
array(['FaceBoom', 'MediaTornado', 'RocketSuperAds', 'TipTop', 'YRabbit',
'AdNonSense', 'LeapBob', 'OppleCreativeMedia', 'WahooNetBanner',
'lambdaMediaAds'], dtype=object)
Дубликатов не обнаружено. В категориальных значениях нет повторяющихся значений.
Изменение типа данных необходимых столбцов.
visits['session_start'] = pd.to_datetime(visits['session_start'])
visits['session_end'] = pd.to_datetime(visits['session_end'])
orders['event_dt'] = pd.to_datetime(orders['event_dt'])
costs['dt'] = pd.to_datetime(costs['dt']).dt.date
Типы данных изменены.
Разрешается использовать функции, с которыми вы познакомились в теоретических уроках.
Это функции для вычисления значений метрик:
get_profiles() — для создания профилей пользователей,get_retention() — для подсчёта Retention Rate,get_conversion() — для подсчёта конверсии,get_ltv() — для подсчёта LTV.А также функции для построения графиков:
filter_data() — для сглаживания данных,plot_retention() — для построения графика Retention Rate,plot_conversion() — для построения графика конверсии,plot_ltv_roi — для визуализации LTV и ROI.# функция для получения пользовательских профилей
def get_profiles(sessions, orders, ad_costs):
profiles = (
sessions.sort_values(by=['user_id', 'session_start'])
.groupby('user_id')
.agg(
{
'session_start': 'first',
'channel': 'first',
'device': 'first',
'region': 'first',
}
)
.rename(columns={'session_start': 'first_ts'})
.reset_index()
)
profiles['dt'] = profiles['first_ts'].dt.date
profiles['month'] = profiles['first_ts'].astype('datetime64[M]')
profiles['payer'] = profiles['user_id'].isin(orders['user_id'].unique())
new_users = (
profiles.groupby(['dt', 'channel'])
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'unique_users'})
.reset_index()
)
ad_costs = ad_costs.merge(new_users, on=['dt', 'channel'], how='left')
ad_costs['acquisition_cost'] = ad_costs['costs'] / ad_costs['unique_users']
profiles = profiles.merge(
ad_costs[['dt', 'channel', 'acquisition_cost']],
on=['dt', 'channel'],
how='left',
)
profiles['acquisition_cost'] = profiles['acquisition_cost'].fillna(0)
return profiles
# функция для расчёта удержания
def get_retention(
profiles,
sessions,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# добавляем столбец payer в передаваемый dimensions список
dimensions = ['payer'] + dimensions
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# собираем «сырые» данные для расчёта удержания
result_raw = result_raw.merge(
sessions[['user_id', 'session_start']], on='user_id', how='left'
)
result_raw['lifetime'] = (
result_raw['session_start'] - result_raw['first_ts']
).dt.days
# функция для группировки таблицы по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
result = df.pivot_table(
index=dims, columns='lifetime', values='user_id', aggfunc='nunique'
)
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
result = result.div(result['cohort_size'], axis=0)
result = result[['cohort_size'] + list(range(horizon_days))]
result['cohort_size'] = cohort_sizes
return result
# получаем таблицу удержания
result_grouped = group_by_dimensions(result_raw, dimensions, horizon_days)
# получаем таблицу динамики удержания
result_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
# возвращаем обе таблицы и сырые данные
return result_raw, result_grouped, result_in_time
# функция для расчёта конверсии
def get_conversion(
profiles,
purchases,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# определяем дату и время первой покупки для каждого пользователя
first_purchases = (
purchases.sort_values(by=['user_id', 'event_dt'])
.groupby('user_id')
.agg({'event_dt': 'first'})
.reset_index()
)
# добавляем данные о покупках в профили
result_raw = result_raw.merge(
first_purchases[['user_id', 'event_dt']], on='user_id', how='left'
)
# рассчитываем лайфтайм для каждой покупки
result_raw['lifetime'] = (
result_raw['event_dt'] - result_raw['first_ts']
).dt.days
# группируем по cohort, если в dimensions ничего нет
if len(dimensions) == 0:
result_raw['cohort'] = 'All users'
dimensions = dimensions + ['cohort']
# функция для группировки таблицы по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
result = df.pivot_table(
index=dims, columns='lifetime', values='user_id', aggfunc='nunique'
)
result = result.fillna(0).cumsum(axis = 1)
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
# делим каждую «ячейку» в строке на размер когорты
# и получаем conversion rate
result = result.div(result['cohort_size'], axis=0)
result = result[['cohort_size'] + list(range(horizon_days))]
result['cohort_size'] = cohort_sizes
return result
# получаем таблицу конверсии
result_grouped = group_by_dimensions(result_raw, dimensions, horizon_days)
# для таблицы динамики конверсии убираем 'cohort' из dimensions
if 'cohort' in dimensions:
dimensions = []
# получаем таблицу динамики конверсии
result_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
# возвращаем обе таблицы и сырые данные
return result_raw, result_grouped, result_in_time
# функция для расчёта LTV и ROI
def get_ltv(
profiles,
purchases,
observation_date,
horizon_days,
dimensions=[],
ignore_horizon=False,
):
# исключаем пользователей, не «доживших» до горизонта анализа
last_suitable_acquisition_date = observation_date
if not ignore_horizon:
last_suitable_acquisition_date = observation_date - timedelta(
days=horizon_days - 1
)
result_raw = profiles.query('dt <= @last_suitable_acquisition_date')
# добавляем данные о покупках в профили
result_raw = result_raw.merge(
purchases[['user_id', 'event_dt', 'revenue']], on='user_id', how='left'
)
# рассчитываем лайфтайм пользователя для каждой покупки
result_raw['lifetime'] = (
result_raw['event_dt'] - result_raw['first_ts']
).dt.days
# группируем по cohort, если в dimensions ничего нет
if len(dimensions) == 0:
result_raw['cohort'] = 'All users'
dimensions = dimensions + ['cohort']
# функция группировки по желаемым признакам
def group_by_dimensions(df, dims, horizon_days):
# строим «треугольную» таблицу выручки
result = df.pivot_table(
index=dims, columns='lifetime', values='revenue', aggfunc='sum'
)
# находим сумму выручки с накоплением
result = result.fillna(0).cumsum(axis=1)
# вычисляем размеры когорт
cohort_sizes = (
df.groupby(dims)
.agg({'user_id': 'nunique'})
.rename(columns={'user_id': 'cohort_size'})
)
# объединяем размеры когорт и таблицу выручки
result = cohort_sizes.merge(result, on=dims, how='left').fillna(0)
# считаем LTV: делим каждую «ячейку» в строке на размер когорты
result = result.div(result['cohort_size'], axis=0)
# исключаем все лайфтаймы, превышающие горизонт анализа
result = result[['cohort_size'] + list(range(horizon_days))]
# восстанавливаем размеры когорт
result['cohort_size'] = cohort_sizes
# собираем датафрейм с данными пользователей и значениями CAC,
# добавляя параметры из dimensions
cac = df[['user_id', 'acquisition_cost'] + dims].drop_duplicates()
# считаем средний CAC по параметрам из dimensions
cac = (
cac.groupby(dims)
.agg({'acquisition_cost': 'mean'})
.rename(columns={'acquisition_cost': 'cac'})
)
# считаем ROI: делим LTV на CAC
roi = result.div(cac['cac'], axis=0)
# удаляем строки с бесконечным ROI
roi = roi[~roi['cohort_size'].isin([np.inf])]
# восстанавливаем размеры когорт в таблице ROI
roi['cohort_size'] = cohort_sizes
# добавляем CAC в таблицу ROI
roi['cac'] = cac['cac']
# в финальной таблице оставляем размеры когорт, CAC
# и ROI в лайфтаймы, не превышающие горизонт анализа
roi = roi[['cohort_size', 'cac'] + list(range(horizon_days))]
# возвращаем таблицы LTV и ROI
return result, roi
# получаем таблицы LTV и ROI
result_grouped, roi_grouped = group_by_dimensions(
result_raw, dimensions, horizon_days
)
# для таблиц динамики убираем 'cohort' из dimensions
if 'cohort' in dimensions:
dimensions = []
# получаем таблицы динамики LTV и ROI
result_in_time, roi_in_time = group_by_dimensions(
result_raw, dimensions + ['dt'], horizon_days
)
return (
result_raw, # сырые данные
result_grouped, # таблица LTV
result_in_time, # таблица динамики LTV
roi_grouped, # таблица ROI
roi_in_time, # таблица динамики ROI
)
# функция для сглаживания фрейма
def filter_data(df, window):
# для каждого столбца применяем скользящее среднее
for column in df.columns.values:
df[column] = df[column].rolling(window).mean()
return df
# функция для визуализации удержания
def plot_retention(retention, retention_history, horizon, window=7):
# задаём размер сетки для графиков
plt.figure(figsize=(15, 10))
# исключаем размеры когорт и удержание первого дня
retention = retention.drop(columns=['cohort_size', 0])
# в таблице динамики оставляем только нужный лайфтайм
retention_history = retention_history.drop(columns=['cohort_size'])[
[horizon - 1]
]
# если в индексах таблицы удержания только payer,
# добавляем второй признак — cohort
if retention.index.nlevels == 1:
retention['cohort'] = 'All users'
retention = retention.reset_index().set_index(['cohort', 'payer'])
# в таблице графиков — два столбца и две строки, четыре ячейки
# в первой строим кривые удержания платящих пользователей
ax1 = plt.subplot(2, 2, 1)
retention.query('payer == True').droplevel('payer').T.plot(
grid=True, ax=ax1
)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Удержание платящих пользователей')
# во второй ячейке строим кривые удержания неплатящих
# вертикальная ось — от графика из первой ячейки
ax2 = plt.subplot(2, 2, 2, sharey=ax1)
retention.query('payer == False').droplevel('payer').T.plot(
grid=True, ax=ax2
)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Удержание неплатящих пользователей')
# в третьей ячейке — динамика удержания платящих
ax3 = plt.subplot(2, 2, 3)
# получаем названия столбцов для сводной таблицы
columns = [
name
for name in retention_history.index.names
if name not in ['dt', 'payer']
]
# фильтруем данные и строим график
filtered_data = retention_history.query('payer == True').pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax3)
plt.xlabel('Дата привлечения')
plt.title(
'Динамика удержания платящих пользователей на {}-й день'.format(
horizon
)
)
# в чётвертой ячейке — динамика удержания неплатящих
ax4 = plt.subplot(2, 2, 4, sharey=ax3)
# фильтруем данные и строим график
filtered_data = retention_history.query('payer == False').pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax4)
plt.xlabel('Дата привлечения')
plt.title(
'Динамика удержания неплатящих пользователей на {}-й день'.format(
horizon
)
)
plt.tight_layout()
plt.show()
# функция для визуализации конверсии
def plot_conversion(conversion, conversion_history, horizon, window=7):
# задаём размер сетки для графиков
plt.figure(figsize=(15, 5))
# исключаем размеры когорт
conversion = conversion.drop(columns=['cohort_size'])
# в таблице динамики оставляем только нужный лайфтайм
conversion_history = conversion_history.drop(columns=['cohort_size'])[
[horizon - 1]
]
# первый график — кривые конверсии
ax1 = plt.subplot(1, 2, 1)
conversion.T.plot(grid=True, ax=ax1)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('Конверсия пользователей')
# второй график — динамика конверсии
ax2 = plt.subplot(1, 2, 2, sharey=ax1)
columns = [
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
name for name in conversion_history.index.names if name not in ['dt']
]
filtered_data = conversion_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax2)
plt.xlabel('Дата привлечения')
plt.title('Динамика конверсии пользователей на {}-й день'.format(horizon))
plt.tight_layout()
plt.show()
# функция для визуализации LTV и ROI
def plot_ltv_roi(ltv, ltv_history, roi, roi_history, horizon, window=7):
# задаём сетку отрисовки графиков
plt.figure(figsize=(20, 10))
# из таблицы ltv исключаем размеры когорт
ltv = ltv.drop(columns=['cohort_size'])
# в таблице динамики ltv оставляем только нужный лайфтайм
ltv_history = ltv_history.drop(columns=['cohort_size'])[[horizon - 1]]
# стоимость привлечения запишем в отдельный фрейм
cac_history = roi_history[['cac']]
# из таблицы roi исключаем размеры когорт и cac
roi = roi.drop(columns=['cohort_size', 'cac'])
# в таблице динамики roi оставляем только нужный лайфтайм
roi_history = roi_history.drop(columns=['cohort_size', 'cac'])[
[horizon - 1]
]
# первый график — кривые ltv
ax1 = plt.subplot(2, 3, 1)
ltv.T.plot(grid=True, ax=ax1)
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('LTV')
# второй график — динамика ltv
ax2 = plt.subplot(2, 3, 2, sharey=ax1)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in ltv_history.index.names if name not in ['dt']]
filtered_data = ltv_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax2)
plt.xlabel('Дата привлечения')
plt.title('Динамика LTV пользователей на {}-й день'.format(horizon))
# третий график — динамика cac
ax3 = plt.subplot(2, 3, 3, sharey=ax1)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in cac_history.index.names if name not in ['dt']]
filtered_data = cac_history.pivot_table(
index='dt', columns=columns, values='cac', aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax3)
plt.xlabel('Дата привлечения')
plt.title('Динамика стоимости привлечения пользователей')
# четвёртый график — кривые roi
ax4 = plt.subplot(2, 3, 4)
roi.T.plot(grid=True, ax=ax4)
plt.axhline(y=1, color='red', linestyle='--', label='Уровень окупаемости')
plt.legend()
plt.xlabel('Лайфтайм')
plt.title('ROI')
# пятый график — динамика roi
ax5 = plt.subplot(2, 3, 5, sharey=ax4)
# столбцами сводной таблицы станут все столбцы индекса, кроме даты
columns = [name for name in roi_history.index.names if name not in ['dt']]
filtered_data = roi_history.pivot_table(
index='dt', columns=columns, values=horizon - 1, aggfunc='mean'
)
filter_data(filtered_data, window).plot(grid=True, ax=ax5)
plt.axhline(y=1, color='red', linestyle='--', label='Уровень окупаемости')
plt.xlabel('Дата привлечения')
plt.title('Динамика ROI пользователей на {}-й день'.format(horizon))
plt.tight_layout()
plt.show()
После каждого пункта сформулируйте выводы.
3.1 Составление профилей пользователей
profiles = get_profiles(visits, orders, costs)
display(profiles.head(), profiles.tail())
| user_id | first_ts | channel | device | region | dt | month | payer | acquisition_cost | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 599326 | 2019-05-07 20:58:57 | FaceBoom | Mac | United States | 2019-05-07 | 2019-05-01 | True | 1.088172 |
| 1 | 4919697 | 2019-07-09 12:46:07 | FaceBoom | iPhone | United States | 2019-07-09 | 2019-07-01 | False | 1.107237 |
| 2 | 6085896 | 2019-10-01 09:58:33 | organic | iPhone | France | 2019-10-01 | 2019-10-01 | False | 0.000000 |
| 3 | 22593348 | 2019-08-22 21:35:48 | AdNonSense | PC | Germany | 2019-08-22 | 2019-08-01 | False | 0.988235 |
| 4 | 31989216 | 2019-10-02 00:07:44 | YRabbit | iPhone | United States | 2019-10-02 | 2019-10-01 | False | 0.230769 |
| user_id | first_ts | channel | device | region | dt | month | payer | acquisition_cost | |
|---|---|---|---|---|---|---|---|---|---|
| 150003 | 999956196527 | 2019-09-28 08:33:02 | TipTop | iPhone | United States | 2019-09-28 | 2019-09-01 | False | 3.500000 |
| 150004 | 999975439887 | 2019-10-21 00:35:17 | organic | PC | UK | 2019-10-21 | 2019-10-01 | False | 0.000000 |
| 150005 | 999976332130 | 2019-07-23 02:57:06 | TipTop | iPhone | United States | 2019-07-23 | 2019-07-01 | False | 2.600000 |
| 150006 | 999979924135 | 2019-09-28 21:28:09 | MediaTornado | PC | United States | 2019-09-28 | 2019-09-01 | False | 0.205714 |
| 150007 | 999999563947 | 2019-10-18 19:57:25 | organic | iPhone | United States | 2019-10-18 | 2019-10-01 | False | 0.000000 |
min_date = profiles['dt'].min()
max_date = profiles['dt'].max()
print(f'Минимальная дата привлечения пользователей {min_date}')
print(f'Максимальная дата привлечения пользователей {max_date}')
Минимальная дата привлечения пользователей 2019-05-01 Максимальная дата привлечения пользователей 2019-10-27
Всего профилей пользователей 150 008.
Минимальная дата привлечения пользователей - 01.05.2019
Максимальная дата привлечения пользователей - 27.10.2019
Результаты проверки соответствуют первоначальным данным.
def create_pivot_table(data, index, columns, values):
pivot_table = pd.pivot_table(data, index=index, columns=columns, values=values, aggfunc='count') \
.rename(columns={True: 'payer', False: 'not_payer'}) \
.sort_values('payer', ascending=False)
pivot_table['share'] = (pivot_table['payer'] / (pivot_table['payer'] + pivot_table['not_payer'])) * 100
pivot_table = pivot_table.sort_values(by='share', ascending=False).style.format({'share': '{:.2f}%'})
return pivot_table
def for_chart(data, index, columns, values):
pivot_table = pd.pivot_table(data, index=index, columns=columns, values=values, aggfunc='count') \
.rename(columns={True: 'payer', False: 'not_payer'}) \
.sort_values('payer', ascending=False)
return pivot_table
3.2 Анализ профилей пользователей по странам
display(create_pivot_table(profiles, 'region','payer', 'user_id'))
fig = px.bar(for_chart(profiles, 'region','payer', 'user_id'),
template='plotly_white', title='Распределение пользователей по странам',
labels=dict(region="регион", value="количество пользователей", variable="тип пользователя"))
fig.show()
| payer | not_payer | payer | share |
|---|---|---|---|
| region | |||
| United States | 93100 | 6902 | 6.90% |
| Germany | 14365 | 616 | 4.11% |
| UK | 16875 | 700 | 3.98% |
| France | 16787 | 663 | 3.80% |
Большиство пользователей из США. А также больше всего платящих пользователей приходится на США. Там на 93100 неплатящих пользователей приходится 6902 платящих, что равно 6.9%.
Великобритания и Франция практически на однмо уровне по показателям общего числа пользователей.
Германия хоть и обладает наименьшим количеством привлеченных пользователей, но находится на втором месте по соотношению платящих и неплатящих.
3.3 Анализ профилей пользователей по устройствам
display(create_pivot_table(profiles, 'device','payer', 'user_id'))
fig = px.bar(for_chart(profiles, 'device','payer', 'user_id'),
template='plotly_white', title='Распределение пользователей по устройствам',
labels=dict(device="device", value="количество пользователей", variable="тип пользователя"))
fig.show()
| payer | not_payer | payer | share |
|---|---|---|---|
| device | |||
| Mac | 28130 | 1912 | 6.36% |
| iPhone | 51097 | 3382 | 6.21% |
| Android | 32982 | 2050 | 5.85% |
| PC | 28918 | 1537 | 5.05% |
Большиство пользователей предпочитают продукцию Apple, в особенности Iphone, где на 51097 неплатящих пользователдей приходится 3382 платящих (6.21%). По соотношению пользователей Iphone занимает 2 место, а Mac - 1ое (6.36%). Меньше всего количество платящих пользователей у PC, где процент соотношения равен 5.05%.
3.4 Анализ профилей пользователей по каналу привлечения
display(create_pivot_table(profiles, 'channel','payer', 'user_id'))
fig = px.bar(for_chart(profiles, 'channel','payer', 'user_id'),
template='plotly_white', title='Распределение пользователей по каналам',
labels=dict(channel="channel", value="количество пользователей", variable="тип пользователя"))
fig.show()
| payer | not_payer | payer | share |
|---|---|---|---|
| channel | |||
| FaceBoom | 25587 | 3557 | 12.20% |
| AdNonSense | 3440 | 440 | 11.34% |
| lambdaMediaAds | 1924 | 225 | 10.47% |
| TipTop | 17683 | 1878 | 9.60% |
| RocketSuperAds | 4096 | 352 | 7.91% |
| WahooNetBanner | 8100 | 453 | 5.30% |
| YRabbit | 4147 | 165 | 3.83% |
| MediaTornado | 4208 | 156 | 3.57% |
| LeapBob | 8291 | 262 | 3.06% |
| OppleCreativeMedia | 8372 | 233 | 2.71% |
| organic | 55279 | 1160 | 2.06% |
В целом большинство пользователей - органические, то есть не были привлечены путем рекламного канала. Однако, процент таких платящих пользователей самый низкий по рейтингу (2.06%).
Больше всего платящих пользователей было привлечено за счет канала FaceBoom (25587 неплатящих на 3557 платящих), где конверсия составила 12.2%.
Также большое количество платящих пользователей было привлечено через TipTop - 1878 на 17683, но конверсия этого канала не самая высокая - 9.6%.
Высокий процент конверсии у канала AdNonSense - 11.34%.
После изучения конверсий пользователей разных регионов, устройств и каналов привлечения можно составить портрет платящего пользователя.
Чаще всего это пользователь:
— из США
— пользуется приложением с мобильного устройства Iphone
— привлечен через рекламный канал FaceBoom
Напишите промежуточные выводы.
4.1 Подсчет общей суммы расходов на маркетинг
sum_costs = costs['costs'].sum().round()
print(f'Общая сумма расходов на маркетинг {sum_costs}')
Общая сумма расходов на маркетинг 105497.0
Общая сумма расходов на маркетинг составила 105497.
4.2 Траты на рекламные источники
channel_costs = costs.pivot_table(index='channel', values='costs', aggfunc='sum').sort_values('costs', ascending=False)
channel_costs_fig = px.bar(channel_costs,
template='plotly_white', title='Траты на рекламные источники',
labels=dict(channel="канал", value="сумма"))
channel_costs_fig.show()
channel_costs['share'] = channel_costs['costs'] / channel_costs['costs'].sum() * 100
channel_costs.style.format({'share': '{:.2f}%'})
| costs | share | |
|---|---|---|
| channel | ||
| TipTop | 54751.300000 | 51.90% |
| FaceBoom | 32445.600000 | 30.75% |
| WahooNetBanner | 5151.000000 | 4.88% |
| AdNonSense | 3911.250000 | 3.71% |
| OppleCreativeMedia | 2151.250000 | 2.04% |
| RocketSuperAds | 1833.000000 | 1.74% |
| LeapBob | 1797.600000 | 1.70% |
| lambdaMediaAds | 1557.600000 | 1.48% |
| MediaTornado | 954.480000 | 0.90% |
| YRabbit | 944.220000 | 0.90% |
Больше всего средств, а именно 51, 9 % от общих затрат на маркетинг было потрачено на рекламные канал TipTop. 30,75 % - на FaceBoom. На каждый из остальных 9 каналов было потрачено от 5 до 1%.
4.3 Динамика изменения расходов по времени
fig = px.line(costs.pivot_table(index='dt', values='costs', aggfunc='sum', columns='channel'),
template='plotly_white', title='Затраты на привлечение пользователей по каналам',
labels=dict(dt="дата", value="затраты на привлечение в день", channel="каналы"))
fig.show()
costs['dt'] = pd.to_datetime(costs['dt'])
costs['month'] = costs['dt'].dt.month
costs['week'] = costs['dt'].dt.week
/tmp/ipykernel_160/1723762270.py:3: FutureWarning: Series.dt.weekofyear and Series.dt.week have been deprecated. Please use Series.dt.isocalendar().week instead.
plt.figure(figsize=(20, 7))
week_costs = costs.pivot_table(index='channel', columns='week', values='costs', aggfunc='sum')
week_costs.T.plot(grid=True, xticks=list(week_costs.columns.values), ax=plt.subplot(1, 2, 1))
plt.title('Динамика расходов по неделям')
month_costs = costs.pivot_table(index='channel', columns='month', values='costs', aggfunc='sum')
month_costs.T.plot(grid=True, xticks=list(month_costs.columns.values), ax=plt.subplot(1, 2, 2))
plt.title('Динамика расходов по неделям')
plt.show()
Траты на маркетинг, а именно: на рекламные каналы FaceBoom и TipTop - с течением времени росли. По графику у канала TipTop можно наблюдать 4 всплеска. 1ый — в начале июня. 2ой — в начале июля. 3ий — в начале августа. Примечательно, что траты увеличивались в начале каждого месяца летом. И сильно уменьшались в середине. С августа по середину сентября траты были достаточно равномерны, но в октябре резко возросли и составили максимальное значение - 630.
Траты на FaceBoom в течение всего периода были достаточно равномерны. Затраты на остальные рекламные источники были равномерны в течение всего периода и не привышали показатели в 2000.
4.4 Привлечение одного пользователя (CAC)
cac = profiles.query('channel != "organic"')[['user_id', 'acquisition_cost']].agg({'acquisition_cost':'mean'})
cac
acquisition_cost 1.127481 dtype: float64
cac_channel = profiles.pivot_table(index='channel', values='acquisition_cost', aggfunc='mean') \
.sort_values(by='acquisition_cost', ascending=False) \
.rename(columns={'acquisition_cost':'cac'})
display(cac_channel.style.format({'cac': '{:.2f}'}))
fig = px.bar(cac_channel,
template='plotly_white', title='Стоимость привлечения пользователей по каналам',
labels=dict(channel="канал", value="сумма"))
fig.show();
| cac | |
|---|---|
| channel | |
| TipTop | 2.80 |
| FaceBoom | 1.11 |
| AdNonSense | 1.01 |
| lambdaMediaAds | 0.72 |
| WahooNetBanner | 0.60 |
| RocketSuperAds | 0.41 |
| OppleCreativeMedia | 0.25 |
| YRabbit | 0.22 |
| MediaTornado | 0.22 |
| LeapBob | 0.21 |
| organic | 0.00 |
В среднем привлечение одного пользователя составило 1.127.
Дороже всего приложению обходится пользователи, привлеченные за счет канала TipTop — 2.8 за пользователя.
На FaceBoom и AdNonSense на одного пользователя приложение тратит всего 1.
Она все остальные меньше единицы.
Общая сумма расходов на маркетинг составила 105497
Почти весь бюджет. — 80%— были потрачены на рекламу FaceBoom и TipTop.
Траты на TipTop с мая по ноябрь интенсивно и скачкообразно росли. Пользователи из данного канала обходятся в приложении дороже всех.
Через другой канал FaceBoom, на который тратятся немного меньше средств, приходят более качественные пользователи (платящие). Траты на этот канал в исследуемый моего период достаточно равномерны. Она одного пользователя приложение тратят в 2 раза меньше, чем на пользователя с канала TipTop.
На хорошей по конверсии канал — AdNonSense, приложение тратит существенно меньше бюджета. Траты производились достаточно равномерно, и один такой пользователь обходится приложению в 1.01.
Стоит сменить фокус внимания с канала TipTop на другой, более качественный канал привлечения пользователей.
Используя графики LTV, ROI и CAC, проанализируйте окупаемость рекламы. Считайте, что на календаре 1 ноября 2019 года, а в бизнес-плане заложено, что пользователи должны окупаться не позднее чем через две недели после привлечения. Необходимость включения в анализ органических пользователей определите самостоятельно.
Напишите вывод, опишите возможные причины обнаруженных проблем и промежуточные рекомендации для рекламного отдела.
5.1 Окупаемость рекламы в целом, конверсия, удержание
profiles = profiles.query('channel != "organic"')
observation_date = datetime(2019, 11, 1).date()
analysis_horizon = 14
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles, orders, \
observation_date, analysis_horizon)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
LTV — достаточно стабилен
CAC — нестабильный показатель.
Сумма расходов на рекламу постоянно растёт.
ROI - не доходит до уровня окупаемости— составляет 80% Более того, он снижается к концу проведения исследования. До июля был выше уровня окупаемости, но после снизился и приблизился к 60 процентам.
conversion_raw, conversion, conversion_history = get_conversion(
profiles,
orders,
observation_date,
analysis_horizon,
)
plot_conversion(conversion, conversion_history, analysis_horizon)
Конверсия пользователей на 2 день составило 7% на 14 день 8. Для крупного приложения с большими расходами на рекламу показатель низкий.
retention_raw, retention_grouped, retention_history = get_retention(
profiles,
visits,
observation_date,
analysis_horizon,
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
Коэффициент удержания платящих пользователей значительно выше, чем они платящих. На 2 день он составляет 0.3 против 0.08.
Самый высокий коэффициент удержания на 14 день у платящих пользователей наблюдаются в июне и составил 0.175. Назначения не опускались ниже 0.06.
Коэффициент удержания неплатящих на 14 день составляет немногим выше нуля, не более 0.01.
5.2 Окупаемость рекламы с разбивкой по устройствам, конверсия, удержание
dimensions=['device']
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles, orders, \
observation_date, analysis_horizon, dimensions
)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
LTV для всех устройств достаточно стабилен.
Стоимость привлечения пользователей резко возросла к июню для всех устройств, особенно для устройств компании Apple (Mac и Iphone)
На всех устройствах пользователи окупались до июня, но после Mac и Iphone резко снизились по уровню окупаемости. Android изредка возвышался над уровнем окупаемости за, но очень нестабильна.
Лучше всего дело обстояло с пользователями PC, стоимость которых является самой низкой, однако и здесь в октябре показатели не пересекали уровень окупаемости. Затраты на привлечение пользователей PC окупаются к 11 дню.
Стоит обратить внимание на пользователей Mac и Iphone, у которых стоимость привлечения высока, но это совсем не окупается.
retention_raw, retention_grouped, retention_history = get_retention(
profiles,
visits,
observation_date,
analysis_horizon,
dimensions
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
В целом динамика удержания пользователей всех платформ не вызывает вопросов. Динамика удержания не платящих пользователей всех устройств стабильно плоха и приближается к нулю. А платящие пользователи PC удерживаются лучше всего в подтверждении предыдущего пункта пользователи устройств Apple выдерживаются хуже остальных.
conversion_raw, conversion, conversion_history = get_conversion(
profiles,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_conversion(conversion, conversion_history, analysis_horizon)
А по показателям конверсии лидируют как раз Mac и Iphone. Меньше всего количество конвертированных пользователей у PC.
Пользователи PC не так хорошо привлекаются, но показывают лучшее значения удержание и окупаемость, в отличие от других устройств.
5.3 Окупаемость рекламы с разбивкой по регионам, конверсия, удержание
dimensions=['region']
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles, orders, \
observation_date, analysis_horizon, dimensions
)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
LTV пользователей у США самый высокий.
Стоимость привлечения пользователей из США резко возросла в июне, продолжала увеличиваться до октября. В пиковой точке была в 4 раза больше стоимости привлеченных пользователей из других регионов.
Стоимость привлечения пользователей из Германии, Франции и Великобритании, наоборот, резко снизилась к июню и была стабильна в течение всего периода.
Несмотря на большие вложения, уровень окупаемости в США самый низкий, но пользователи данного региона составляют основной массив приложения.
Лучший процент окупаемости у пользователей из Великобритании и Германии. На графике диканими ROI также особо выделяются резкие скачки в июле и августе у Великобритании. Возможно, это было связано с проведением аквтиных рекламных кампаний в этот период для данного региона.
Стоит обратить внимание на основную аудиторию— пользователей США.
retention_raw, retention_grouped, retention_history = get_retention(
profiles,
visits,
observation_date,
analysis_horizon,
dimensions
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
Удержание платящих пользователей из США слабее чем пользователей и других регионов. Невозможно выделить явного лидера по удержанию платящих пользователей.
Показатели неплатящих пользователей во всех регионах критически низкие.
conversion_raw, conversion, conversion_history = get_conversion(
profiles,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_conversion(conversion, conversion_history, analysis_horizon)
Конверсия пользователей Великобритании, Германии и Франции в 2 раза слабее, чем у пользователей США. Но и их доля относительно общего количества мала.
Кроме того, на графике динамики конверсии видно, что в конце мая во всех европейских странах конверсия резко возрасла и также резка упала уже в начале июня. Возможно, в этот период компания решила сфокусироваться на привлечение пользователей из США, хотя раньше вела другую политику.
Самая многочисленная категория — пользователи из США -также самая "тяжелая" для приложения. На привлечение пользователей идет существенная часть бюджета. Но при этом пользователи не удерживаются и не окупаются.
5.4 Окупаемость рекламы с разбивкой по рекламным каналам, конверсия, удержание
dimensions=['channel']
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles, orders, \
observation_date, analysis_horizon, dimensions
)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
LTV существенно различаются у всех каналов. TipTop и lambdaMediaAds ближе остальных находятся друг к другу.
Самый высокий показатель LTV у lambdaMediaAds.
Начиная с середины мая и постепенно увеличиваясь к октябрю, можно наблюдать интенсивный рост стоимости привлеченных пользователей только для рекламного канала TipTop. Несмотря на это, у данного канала самые низкие показатели окупаемости.
Не окупаются вложения в рекламные каналы: AdNonSense и Faceboom.
Лучше всего в опросе окупаемости ведут себя рекламные каналы, на которые было потрачено меньшее количество бюджета. По графику динамики ROI хорошо себя показал канал YRabbit, окупаемость которого была достаточно высока в период с конца июня по середину сентября.
retention_raw, retention_grouped, retention_history = get_retention(
profiles,
visits,
observation_date,
analysis_horizon,
dimensions
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
Хуже всего показателя удержания платящих пользователей у каналов Faceboom и AbNonSense. У остальных явного лидера не наблюдается.
Показатели удержания не платящих пользователей стабильно низкие и равномерны.
conversion_raw, conversion, conversion_history = get_conversion(
profiles,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_conversion(conversion, conversion_history, analysis_horizon)
Лучшие показатели конверсии у многочисленных по количеству привлеченных пользователей каналов в Faceboom, AdNonSense, lambdaMediaAds и TipTop.
5.5 Окупаемость рекламы с разбивкой по рекламным каналам и регионам, конверсия, удержание
Разделение профилей пользователей на пользователей США и Европы.
profiles_usa = profiles.query('region == "United States"')
profiles_others = profiles.query('region != "United States"')
Анализ окупаемости рекламы с разбивкой по рекламным каналам для США
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles_usa,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
LTV пользователей TipTop самый высокий, данный канал самый многочисленный по количеству привлеченных пользователей. Динамика LTV каждого канала скачкообразна, но периодически стабоильна. С мая по середину июня канал RocketSuperAds показывал высокие значения, почти приближенные к TipTop.
По динамике стоимости привлечения пользователей видно, что стоимость канала TipTop увеличивается в конце каждого месяца. Стоимость канала FaceBoom абсолютно стабильна. А затраты на остальные каналы к концу исследуемого периода сравняла и не показывали высроких результатов.
Рекламные каналы TipTop и FaceBoom не окупатся, но к концу периода приближаются к уровню окупаемости. Остальные каналы привлечения окупаются уже на второй день.
По динамике Roi заметно, канал Yrabbit во весь летний период показывал масимально высокие показатели окупаеамости. В сентябре у MediaTorando наблюдался сильный скачок. К концу исследуемого периода каналы YRabiit, MediaTornado и RocketSuperAds показывают равномерные результаты.
retention_raw, retention_grouped, retention_history = get_retention(
profiles_usa,
visits,
observation_date,
analysis_horizon,
dimensions
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
Хорошие и стабильные показатели удержания показывает канал TipTop. Пользователи канала Faceboom удерживаются хуже остальных.
RocketSuperAds показывал высокое удержание, периодически. Видимо, не всегда пользователи "доживают" до 14 дня.
В мае мельком на графике появляются MediaTornado и YRabbit, пользователи котороых также вероятно не задерживаются в приложении на 14 дней.
conversion_raw, conversion, conversion_history = get_conversion(
profiles_usa,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_conversion(conversion, conversion_history, analysis_horizon)
Топ 3 лучших показателей конверсии: Faceboom, TipTop И RocketSuperAds.
Судя по всему все перспективы быть оптимльным каналом привлечения В США по всем показателям есть у RocketSuperAds.
Анализ окупаемости рекламы с разбивкой по рекламным каналам для европейских стран
ltv_raw, ltv_grouped, ltv_history, roi_grouped, roi_history = get_ltv(profiles_others,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_ltv_roi(ltv_grouped, ltv_history, roi_grouped, roi_history, analysis_horizon)
Самый высокий показатель LTV у lambdaMediaAds. Это также заметно по динамике LTV в течение всегот периода.
Стоимость привлечения пользователей увсех каналдов равномерна. Самый дорогостоящий канал - AdNonSense, после него lambdaMediaAds.
Все каналы привлечения,кроме AdNonSense окупаются. lambdaMediaAds и Leapbob окупаются уже с 2 дня пользования, а OppleCreativeMedia и WahooNetBanner с 5 дня. lambdaMediaAds и Leapbob имеют самые высокие показатели и по динамике RoI за весь период.
retention_raw, retention_grouped, retention_history = get_retention(
profiles_others,
visits,
observation_date,
analysis_horizon,
dimensions
)
plot_retention(retention_grouped, retention_history, analysis_horizon)
AdNonSense показывает очень низкое по сравнению с остальными удержание платящих пользователей.
conversion_raw, conversion, conversion_history = get_conversion(
profiles_others,
orders,
observation_date,
analysis_horizon,
dimensions
)
plot_conversion(conversion, conversion_history, analysis_horizon)
Самые высокие показатели конверсии наблюдались у каналов AdNonSense и lambdaMediaAds
Реклама не окупается.
Негативное влияние на окупаемость оказывают рекламные каналы: Faceboom и TipTop для США и AdNonSense для европейских стран.
Низкая окупаемость рекламы также наблюдается в США, где конверсия пользователей хороша, что не скажешь об удержании.
Устройство Apple (Mac и Iphone) не окупаются. Устройства PC имеют более низкие показатели конверсии, но процент удержания таких пользователей выше, а также имеется перспектива к окупаемости.
— неправильным распределением ресурсов на рекламные каналы Faceboom, AdNonSense, и TipTop.
— резкой сменой рекламной политики и внедрением нововведений в работу приложения, что можно было заметить по резким скачкам показателей начиная с июня 2019.
В ходе проведения анализа бизнес-показателей приложения Procrastinate Pro+ было выполнено:
По результатам исследования стало очевидно, что основной причиной финансовых проблем стали рекламные траты на привлечение пользователей США через рекламные каналы Faceboom и TipTop, а пользователей Европы через AdNonSense.
У Faceboom самый низкий показатель удержания пользователей. Несмотря на среднюю стоимость, канал не окупается.
На канал TipTop приложение затрачивает большую часть бюджета на маркетинг, однако канал не окупается, хотя и показывает хороший процент удержания пользователей.
AdNonSense самый дорогостоящий для европейских тсран канал привлечения. Однако, у него самые низкие показатели удержания, и это единственный канал для Европы, которые не окупается за 14 дней.
У данных каналов самая высокая стоимость привлечения и конверсии, но они не окупаются и показывают плохие показатели удержания пользователей. Проблемы окупаемости могут быть вызваны ненормированным распределением средств на рекламую В данные источники была вложена большая часть бюджета, но они не показали своей эффективности и не оправдали надедж.
Также, наименее привлекательными с точки зрения бизнеса являются пользователи Apple устройств, возможно, есть проблема с оптимизацией приложения.
Исходя из этого, компании можно дать следующие рекомендации:
— стоит попробовать сменить вектор с пользователей из США на пользователей из Великобритании и Германии
— необходимо развивать те каналы, которые позволяют эффективнее доносить ценность продукта и имеют перспективные и сбалансированные показатели LTV, ROI, стоимости, удержания и конверсии
— стоит обратить внимание на более стабильные рекламные каналы:
В США: RocketSuperAds (высокие показатели ROI,окупаемости и удержания), YRabbit( лучший показатель ROI и хорошие показетли удержания), MediaTornado (хороший показатель ROI, низкая стоимости и неплохое удержание)
В Европе: lambdaMediaAds (высокие показатели ROI, LTV, окупаемости, удержания и конверсии) и, возможно, WahooNetBanner (не такие высокие показатели, как у lambdaMediaAds, но они устредненны и стабильны)
— привлекать больше PC пользователей, которые являются потенциально более прибыльными за счет хороших показателей удержания